//
// Copyright (C) 2010 Tarmin Rehve <tarmin.rehve@gmail.com>
//
//This file is part of the LibSndFile.NET software library.
//
//    LibSndFile.NET is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    LibSndFile.NET is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Lesser License for more details.
//
//    You should have received a copy of the GNU Lesser Public License
//    along with LibSndFile.NET.  If not, see <http://www.gnu.org/licenses/>.


#pragma once

#include <sndfile.h>
#include <windows.h>
#include <io.h>
#include <Fcntl.h>

using namespace System;
using namespace System::Runtime::Remoting;
using namespace System::Runtime::InteropServices;
using namespace System::IO;

namespace LibSndFile {

    public ref struct SizeOfType
    {
    private:
        const TypeCode typeCode;

    public:
        SizeOfType( TypeCode typecode ):typeCode(typecode){;}

        static operator int(SizeOfType% sizeOf)
        {
            int size;
            switch( sizeOf.typeCode )
            {
            case TypeCode::Int16:
                size = sizeof(Int16);
                break;
            case TypeCode::Byte:
                size = sizeof(Byte);
                break;
            case TypeCode::Boolean:
                size = sizeof(Boolean);
                break;
            case TypeCode::Char:
                size = sizeof(Char);
                break;
            case TypeCode::DateTime:
                size = sizeof(DateTime);
                break;
            case TypeCode::DBNull:
                throw gcnew ArgumentException( "SizeOfType() cannot be determined with TypeCode::DBNull" );
                break;
            case TypeCode::Decimal:
                size = sizeof(Decimal);
                break;
            case TypeCode::Double:
                size = sizeof(Double);
                break;
            case TypeCode::Empty:
                throw gcnew ArgumentException( "SizeOfType() cannot be determined with TypeCode::Empty" );
                break;
            case TypeCode::Int32:
                size = sizeof(Int32);
                break;
            case TypeCode::Int64:
                size = sizeof(Int64);
                break;
            case TypeCode::Object:
                throw gcnew ArgumentException( "SizeOfType() cannot be determined with TypeCode::Object" );
                break;
            case TypeCode::SByte:
                size = sizeof(SByte);
                break;
            case TypeCode::Single:
                size = sizeof(Single);
                break;
            case TypeCode::String:
                throw gcnew ArgumentException( "SizeOfType() cannot be determined with TypeCode::String" );
                break;
            case TypeCode::UInt16:
                size = sizeof(UInt16);
                break;
            case TypeCode::UInt32:
                size = sizeof(UInt32);
                break;
            case TypeCode::UInt64:
                size = sizeof(UInt64);
                break;
            }

            return size;
        }

        property int Value{ int get() { return (int) *this; } }
    };

    
    [SerializableAttribute]
    [ComVisibleAttribute(true)]
    public enum class SndFileMajorFormat
    {
    	WAV = SF_FORMAT_WAV,        /* Microsoft WAV format (little endian default). */
    	AIFF = SF_FORMAT_AIFF,      /* Apple/SGI AIFF format (big endian). */
    	AU = SF_FORMAT_AU,          /* Sun/NeXT AU format (big endian). */
    	RAW = SF_FORMAT_RAW,        /* RAW PCM data. */
    	PAF = SF_FORMAT_PAF,        /* Ensoniq PARIS file format. */
    	SVX = SF_FORMAT_SVX,        /* Amiga IFF / SVX8 / SV16 format. */
    	NIST = SF_FORMAT_NIST,      /* Sphere NIST format. */
    	VOC = SF_FORMAT_VOC,        /* VOC files. */
    	IRCAM = SF_FORMAT_IRCAM,    /* Berkeley/IRCAM/CARL */
    	W64 = SF_FORMAT_W64,        /* Sonic Foundry's 64 bit RIFF/WAV */
	    MAT4 = SF_FORMAT_MAT4,      /* Matlab (tm) V4.2 / GNU Octave 2.0 */
	    MAT5 = SF_FORMAT_MAT5,      /* Matlab (tm) V5.0 / GNU Octave 2.1 */
	    PVF = SF_FORMAT_PVF,        /* Portable Voice Format */
	    XI = SF_FORMAT_XI,          /* Fasttracker 2 Extended Instrument */
	    HTK = SF_FORMAT_HTK,        /* HMM Tool Kit format */
	    SDS = SF_FORMAT_SDS,        /* Midi Sample Dump Standard */
	    AVR = SF_FORMAT_AVR,        /* Audio Visual Research */
	    WAVEX = SF_FORMAT_WAVEX,    /* MS WAVE with WAVEFORMATEX */
	    SD2 = SF_FORMAT_SD2,        /* Sound Designer 2 */
	    FLAC = SF_FORMAT_FLAC,      /* FLAC lossless file format */
	    CAF = SF_FORMAT_CAF,        /* Core Audio File format */
	    WVE = SF_FORMAT_WVE,        /* Psion WVE format */
	    OGG = SF_FORMAT_OGG,        /* Xiph OGG container */
	    MPC2K = SF_FORMAT_MPC2K,    /* Akai MPC 2000 sampler */
	    RF64 = SF_FORMAT_RF64       /* RF64 WAV file */
    };

    [SerializableAttribute]
    [ComVisibleAttribute(true)]
    public enum class SndFileSubFormat
    {
	    PCM_S8 = SF_FORMAT_PCM_S8,          /* Signed 8 bit data */
	    PCM_16 = SF_FORMAT_PCM_16,          /* Signed 16 bit data */
	    PCM_24 = SF_FORMAT_PCM_24,          /* Signed 24 bit data */
	    PCM_32 = SF_FORMAT_PCM_32,          /* Signed 32 bit data */

	    PCM_U8 = SF_FORMAT_PCM_U8,          /* Unsigned 8 bit data (WAV and RAW only) */

	    FLOAT = SF_FORMAT_FLOAT,            /* 32 bit float data */
	    DOUBLE = SF_FORMAT_DOUBLE,          /* 64 bit float data */

	    ULAW = SF_FORMAT_ULAW,              /* U-Law encoded. */
	    ALAW = SF_FORMAT_ALAW,              /* A-Law encoded. */
	    IMA_ADPCM = SF_FORMAT_IMA_ADPCM,    /* IMA ADPCM. */
	    MS_ADPCM = SF_FORMAT_MS_ADPCM,      /* Microsoft ADPCM. */

	    GSM610 = SF_FORMAT_GSM610,          /* GSM 6.10 encoding. */
	    VOX_ADPCM = SF_FORMAT_VOX_ADPCM,    /* OKI / Dialogix ADPCM */

	    G721_32 = SF_FORMAT_G721_32,        /* 32kbs G721 ADPCM encoding. */
	    G723_24 = SF_FORMAT_G723_24,        /* 24kbs G723 ADPCM encoding. */
	    G723_40 = SF_FORMAT_G723_40,        /* 40kbs G723 ADPCM encoding. */

	    DWVW_12 = SF_FORMAT_DWVW_12,        /* 12 bit Delta Width Variable Word encoding. */
	    DWVW_16 = SF_FORMAT_DWVW_16,        /* 16 bit Delta Width Variable Word encoding. */
	    DWVW_24 = SF_FORMAT_DWVW_24,        /* 24 bit Delta Width Variable Word encoding. */
	    DWVW_N = SF_FORMAT_DWVW_N,          /* N bit Delta Width Variable Word encoding. */

	    DPCM_8 = SF_FORMAT_DPCM_8,          /* 8 bit differential PCM (XI only) */
	    DPCM_16 = SF_FORMAT_DPCM_16,        /* 16 bit differential PCM (XI only) */

	    VORBIS = SF_FORMAT_VORBIS           /* Xiph Vorbis encoding. */
    };
    
    [SerializableAttribute]
    [ComVisibleAttribute(true)]
    public ref struct SndFileInfo
    {
        int SamplesPerSecond;
        int Channels;
        int Format;
    };

    // a managed class wrapper for the functions of the libsndfile library
    [ComVisibleAttribute(true)]
    public ref class SndFile: public MarshalByRefObject
    {
    protected:
        SNDFILE* 	sndfileHandle;
        SndFileInfo^ sndFileInfo;
        IO::FileStream^ const InnerFileStream;

    public:
        SndFile(
            [InAttribute] FileStream^ const innerfileStream,
            [InAttribute] [OutAttribute] SndFileInfo^ info ):
        InnerFileStream( innerfileStream )
        {
            SF_INFO sf_info;
            // native file handle
            IntPtr intPtr = innerfileStream->SafeFileHandle->DangerousGetHandle();
            int sf_mode;
            int _Flags;

            sndFileInfo = info;

            sf_info.samplerate = info->SamplesPerSecond;
            sf_info.channels = info->Channels;
            sf_info.format = info->Format;
            
            if( innerfileStream->CanRead )
            {
                if( innerfileStream->CanWrite )
                {
                    sf_mode = SFM_RDWR;
                    _Flags = _O_RDWR;
                }
                else
                {
                    sf_mode = SFM_READ;
                    _Flags = _O_RDONLY;
                }
            }
            else
            {
                sf_mode = SFM_WRITE;
                _Flags = _O_WRONLY;
            }

            // native file descriptor
            int fd = _open_osfhandle( (intptr_t) intPtr.ToPointer(), _Flags );
            if( fd < 0 )
                throw gcnew IOException( "_open_osfhandle() was not able to return a valid file descriptor");
            

            sndfileHandle = sf_open_fd( fd, sf_mode, &sf_info, FALSE );
            if( sndfileHandle == NULL )
            {                
                //TODO: return a sndfile error code
                throw gcnew IOException( "sf_opend_fd() has returned a null handle");
            }

            info->SamplesPerSecond = sf_info.samplerate;
            info->Channels = sf_info.channels;
            info->Format = sf_info.format;
        }

        !SndFile()
        {
            int sf_return;

            sf_write_sync( sndfileHandle );
            sf_return = sf_close( sndfileHandle );

            Diagnostics::Debug::Assert( sf_return == 0, "sf_close has returned a non null value:" + sf_return );
        }
        ~SndFile(){ this->!SndFile(); }

        generic <typename T> where T: value class long long Read( interior_ptr<T> items, long long countOfItems )
        {
            long long countOfItemsRead;
            if( T::typeid == short::typeid )
                countOfItemsRead = Read( (interior_ptr<short>) items, countOfItems );
            else if(T::typeid == int::typeid)
                countOfItemsRead = Read( (interior_ptr<int>) items, countOfItems );
            else if(T::typeid == float::typeid)
                countOfItemsRead = Read( (interior_ptr<float>) items, countOfItems );
            else if(T::typeid == double::typeid)
                countOfItemsRead = Read( (interior_ptr<double>) items, countOfItems );
            else
                throw gcnew InvalidDataException( T::typeid->Name + " is not a supported type for the parameter items" );

            return countOfItemsRead;
        }

        long long Read( interior_ptr<short> items, long long countOfItems)
        {
            pin_ptr<short> start_ptr = items;

            if( countOfItems > 0 )
                return sf_read_short( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
       }

        long long Read( interior_ptr<int> items, long long countOfItems)
        {
            pin_ptr<int> start_ptr = items;

            if( countOfItems > 0 )
                return sf_read_int( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        long long Read( interior_ptr<float> items, long long countOfItems)
        {
            pin_ptr<float> start_ptr = items;

            if( countOfItems > 0 )
                return sf_read_float( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        long long Read( interior_ptr<double> items, long long countOfItems)
        {
            pin_ptr<double> start_ptr = items;

            if( countOfItems > 0 )
                return sf_read_double( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        generic <typename T> where T: value class long long Read( array<T>^ items, long long countOfItems)
        {
            return Read( &items[0], countOfItems );
        }

        long long Read( Array^ items, Type^ itemType, long long countOfItems)
        {
            switch( Type::GetTypeCode(itemType) )
            {
            case TypeCode::Int16:
                return Read( (array<short>^) items, countOfItems );
                break;
            case TypeCode::Int32:
                return Read( (array<int>^) items, countOfItems );
                break;
            case TypeCode::Single:
                return Read( (array<float>^) items, countOfItems );
                break;
            case TypeCode::Double:
                return Read( (array<double>^) items, countOfItems );
                break;
            default:
                throw gcnew ArgumentException( "type" + itemType->Name + " is not valid for itemType parameter" );
            }
        }

        generic <typename T> where T: value class long long Write( interior_ptr<T> items, long long countOfItems )
        {
            long long countOfItemsWritten;
            if( T::typeid == short::typeid )
                countOfItemsWritten = Write( (interior_ptr<short>) items, countOfItems );
            else if(T::typeid == int::typeid)
                countOfItemsWritten = Write( (interior_ptr<int>) items, countOfItems );
            else if(T::typeid == float::typeid)
                countOfItemsWritten = Write( (interior_ptr<float>) items, countOfItems );
            else if(T::typeid == double::typeid)
                countOfItemsWritten = Write( (interior_ptr<double>) items, countOfItems );
            else
                throw gcnew InvalidDataException( T::typeid->Name + " is not a supported type for the parameter items" );

            return countOfItemsWritten;
        }

        long long Write( interior_ptr<short> items, long long countOfItems)
        {
            pin_ptr<short> start_ptr = items;

            if( countOfItems > 0 )
                return sf_write_short( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        long long Write( interior_ptr<int> items, long long countOfItems)
        {
            pin_ptr<int> start_ptr = items;

            if( countOfItems > 0 )
                return sf_write_int( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        long long Write( interior_ptr<float> items, long long countOfItems)
        {
            pin_ptr<float> start_ptr = items;

            if( countOfItems > 0 )
                return sf_write_float( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        long long Write( interior_ptr<double> items, long long countOfItems)
        {
            pin_ptr<double> start_ptr = items;

            if( countOfItems > 0 )
                return sf_write_double( sndfileHandle, start_ptr, countOfItems);
            else
                return 0;
        }

        generic <typename T> where T: value class long long Write( array<T>^ items, long long countOfItems)
        {
            return Write( &items[0], countOfItems );
        }

        long long Write( Array^ items, Type^ itemType, long long countOfItems)
        {
            switch( Type::GetTypeCode(itemType) )
            {
            case TypeCode::Int16:
                return Write( (array<short>^) items, countOfItems );
                break;
            case TypeCode::Int32:
                return Write( (array<int>^) items, countOfItems );
                break;
            case TypeCode::Single:
                return Write( (array<float>^) items, countOfItems );
                break;
            case TypeCode::Double:
                return Write( (array<double>^) items, countOfItems );
                break;
            default:
                throw gcnew ArgumentException( "type" + itemType->Name + " is not valid for itemType parameter" );
            }
        }
    };
}
